﻿using System;
using System.Data;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using System.Threading;

namespace Microsoft.SqlCommunity.SQLRSS
{
    /// <summary>
    /// The base class for all objects which are created based on XML in the XML configuration file.  Because these objects
    /// can be potentially accessed by multiple threads this class provides some core concurrency mechanisms.  See also 
    /// ProtectedDictionary.cs for additional (generic) concurrency mechanisms.
    /// </summary>
    public abstract class Description
    {
        // The schema name for the schema that the XML configuration file must comply with.
        public static readonly XNamespace fm = "http://schemas.microsoft.com/sqlcommunity/2008/3/RSSData/FeedMap.xsd";

        // What kind of lock can be held on this object
        public enum LockType : byte {Read, Upgradable, Write};

        // What is executed within the context of a lock.  See the first overload of ExecuteWithLock below.
        public delegate void OperationWithinLock();

        // The internal locking implementation.
        protected ReaderWriterLockSlim _Lock = new ReaderWriterLockSlim();

        // A simple helper method which returns null if an attribute is not present, otherwise returns
        // the value of the attribute.
        protected static string AttributeValueOrNull(XElement xElement, string attributeName)
        {
            XAttribute xAttribute = xElement.Attribute(attributeName);
            if (xAttribute == null) return null;
            return xAttribute.Value;
        }

        // A simple helper method which returns null if an element is not present, otherwise returns 
        // the value of the element.  This is suitable for elements which only contains a simple type
        // (like a string).
        protected static string ElementValueOrNull(XElement xElement, string elementName)
        {
            XElement childElement = xElement.Element(fm + elementName);
            if (childElement == null) return null;
            return childElement.Value;
        }

        /// <summary>
        /// Without exposing our locking mechanism to the caller, execute an operation within the context 
        /// of the requested lock type.  For this overload there are no parameters or return values.
        /// </summary>
        /// <param name="requestedLockType">What type of lock to acquire before executing the operation.</param>
        /// <param name="operation">What to do after the lock is acquired.</param>
        public void ExecuteWithLock(LockType requestedLockType, OperationWithinLock operation)
        {
            //If only we had a macro facility...
            try
            {
                //Acquire a lock based on the requested lock type
                switch (requestedLockType)
                {
                    case LockType.Read:
                        _Lock.EnterReadLock();
                        break;

                    case LockType.Upgradable:
                        _Lock.EnterUpgradeableReadLock();
                        break;

                    case LockType.Write:
                        _Lock.EnterWriteLock();
                        break;

                    default:
                        throw new ArgumentException(string.Format("{0} is an invalid lock type.", requestedLockType.ToString()),
                            "requestedLockType");
                }
                //Invoke the operation our caller provided.
                operation();
            }
            finally
            {
                //Make sure to release whatever lock might be held at this point.  Note that it might *not* be the one
                //initially requested.
                if (_Lock.IsWriteLockHeld) _Lock.ExitWriteLock();
                if (_Lock.IsUpgradeableReadLockHeld) _Lock.ExitUpgradeableReadLock();
                if (_Lock.IsReadLockHeld) _Lock.ExitReadLock();
            }
        }

        /// <summary>
        /// Without exposing our locking mechanism to the caller, execute an operation within the context 
        /// of the requested lock type.  For this overload there are no parameters but there is a return value.
        /// </summary>
        /// <typeparam name="TResult">What type of value which will be returned from invoking the operation.</typeparam>
        /// <param name="requestedLockType">What type of lock to acquire before invoking the operation.</param>
        /// <param name="function">What operation to invoke after the the lock is acquired.</param>
        /// <returns>What invoking the operation returns!</returns>
        public TResult ExecuteWithLock<TResult>(LockType requestedLockType, Func<TResult> function)
        {
            try
            {
                //Acquire a lock based on the requested lock type
                switch (requestedLockType)
                {
                    case LockType.Read:
                        _Lock.EnterReadLock();
                        break;

                    case LockType.Upgradable:
                        _Lock.EnterUpgradeableReadLock();
                        break;

                    case LockType.Write:
                        _Lock.EnterWriteLock();
                        break;
                }
                //Invoke the operation our caller provided and return the value returned from invoking the operation.
                return function();
            }
            finally
            {
                //Make sure to release whatever lock might be held at this point.  Note that it might *not* be the one
                //initially requested.
                if (_Lock.IsWriteLockHeld) _Lock.ExitWriteLock();
                if (_Lock.IsUpgradeableReadLockHeld) _Lock.ExitUpgradeableReadLock();
                if (_Lock.IsReadLockHeld) _Lock.ExitReadLock();
            }
        }
    }
}
